﻿using DNFImagePacks2Manager.Helpers;
using DNFImagePacks2Manager.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;

namespace DNFImagePacks2Manager.NPK
{
    public class NPKFile
    {
        private readonly string? readPath = null;
        public bool IsValidated { get; private set; } = false;
        public NPKHeader? Header { get; set; }
        public Dictionary<string, NPKIndex> npkIndexs { get; set; } = [];
        private byte[] checksumBytes = [];

        public NPKFile(List<NPKIndex> imgList)
        {
            Header = new NPKHeader(imgList.Count);
            foreach (var img in imgList)
            {
                npkIndexs.Add(img.Name, img);
            }
        }

        public NPKFile(string path)
        {
            try
            {
                using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
                using var reader = new BinaryReader(fileStream);
                Header = new(reader);
                if (Header.ValidateFlag())
                {
                    List<NPKIndex> indexList = [];
                    int imgCount = Header.Count;
                    if (imgCount > 0)
                    {
                        for (int i = 0; i < imgCount; i++)
                        {
                            NPKIndex index = new(reader);
                            if (index.Name.Contains(".ogg"))
                            {
                                npkIndexs.Add(index.Name, index);
                            }
                            else if (index.Name.Contains(".img") && index.Size > 32 && index.Name.StartsWith("sprite/"))
                            {
                                npkIndexs.Add(index.Name, index);
                            }
                            indexList.Add(index);
                        }
                    }
                    checksumBytes = reader.ReadBytes(32);
                    IsValidated = ValidateChecksum(indexList);
                    if (IsValidated)
                    {
                        foreach (var imgIndex in indexList)
                        {
                            imgIndex.ReadItemBytes(reader);
                        }
                    }
                }
                Header.Count = npkIndexs.Count;
            }
            catch (Exception) { }
        }

        public NPKIndex? GetImgIndex(string imgName)
        {
            if (npkIndexs.TryGetValue(imgName, out NPKIndex? value))
            {
                return value;
            }
            else
            {
                return null;
            }
        }

        public IMGFile? GetImgFile(string imgName)
        {
            IMGFile? img = null;
            if (npkIndexs.TryGetValue(imgName, out NPKIndex? imgIndex))
            {
                if (readPath != null)
                {
                    using var fileStream = new FileStream(readPath, FileMode.Open, FileAccess.Read);
                    using var reader = new BinaryReader(fileStream);
                    img = imgIndex.GetIMGFile(reader);
                }
                else
                {
                    img = imgIndex.GetIMGFile();
                }
            }
            return img;
        }

        public static NPKIndex? ReadIMGFromNPK(NPKItemBase npk, string imgName)
        {
            string path = npk.FilePath;
            if (File.Exists(path)) {
                try
                {
                    using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
                    using var reader = new BinaryReader(fileStream);
                    NPKHeader Header = new(reader);
                    if (Header.ValidateFlag())
                    {
                        int imgCount = Header.Count;
                        if (imgCount > 0)
                        {
                            for (int i = 0; i < imgCount; i++)
                            {
                                NPKIndex index = new(reader);
                                if (string.Equals(imgName, index.Name) && index.Size > 0)
                                {
                                    reader.BaseStream.Position = index.Offset;
                                    index.ReadItemBytes(reader);
                                    return index;
                                }
                            }
                        }
                    }
                }
                catch (Exception) { }
            }
            return null;
        }

        public static NPKFile? ReadFromNPK(NPKItemBase npk)
        {
            string? npkFile = null;
            if (npk.IsFileExist)
            {
                if (File.Exists(npk.EnableFile))
                {
                    npkFile = npk.EnableFile;
                }
                else if (File.Exists(npk.DisableFile))
                {
                    npkFile = npk.DisableFile;
                }
            }
            if (npkFile == null)
            {
                return null;
            }
            else
            {
                return new NPKFile(npkFile);
            }
        }

        public void WriteToFile(string path)
        {
            if(Header != null)
            {
                int offset = 0;
                offset += 20;
                offset += npkIndexs.Count * 264;
                offset += 32;
                List<byte> headBytes = [];
                Header.Count = npkIndexs.Count;
                headBytes.AddRange(Header.GetBytes());
                List<byte[]> imgBytesList = [];
                foreach (var imgIndex in npkIndexs.Values)
                {
                    byte[] imgBytes;
                    if (imgIndex.Name.Contains(".ogg"))
                    {
                        imgBytes = imgIndex.ItemBytes;
                    }
                    else
                    {
                        imgBytes = imgIndex.GetIMGFile()?.GetBytes() ?? [];
                    }
                    imgBytesList.Add(imgBytes);
                    int size = imgBytes.Length;
                    imgIndex.Offset = offset;
                    imgIndex.Size = size;
                    offset += size;
                    headBytes.AddRange(imgIndex.GetBytes());
                }
                checksumBytes = CalculateChecksumBytes([.. headBytes]);
                headBytes.AddRange(checksumBytes);

                using FileStream fs = new(path, FileMode.Create, FileAccess.Write);
                using BinaryWriter writer = new(fs);
                writer.Write([.. headBytes]);
                foreach (var imgBytes in imgBytesList)
                {
                    writer.Write(imgBytes);
                }
            }
        }

        public static List<SoftwareBitmap> GetPreviewImagesFromPath(string path)
        {
            List<SoftwareBitmap> list = [];

            try
            {
                using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
                using var reader = new BinaryReader(fileStream);
                NPKHeader Header = new(reader);
                if (Header.ValidateFlag())
                {

                    int imgCount = Header.Count;
                    if (imgCount > 0)
                    {
                        long indexOffset = reader.BaseStream.Position;
                        int max = LocalStorageHelper.GetNPKPreviewImageCount();
                        int minWidth = LocalStorageHelper.GetNPKPreviewMinWidth();
                        int minHeight = LocalStorageHelper.GetNPKPreviewMinHeight();
                        int countPer = LocalStorageHelper.GetNPKPreviewPerCount();
                        for (int i = 0; i < imgCount; i++)
                        {
                            if (list.Count >= max)
                            {
                                break;
                            }
                            reader.BaseStream.Position = indexOffset;
                            NPKIndex index = new(reader);
                            indexOffset = reader.BaseStream.Position;
                            if(index.Size > 32)
                            {
                                reader.BaseStream.Position = index.Offset;
                                IMGFile? img = index.GetIMGFile(reader);
                                if (img != null) {
                                    int countIMG = countPer;
                                    foreach (ImageFile image in img.Images)
                                    {
                                        if (list.Count >= max || countIMG < 1)
                                        {
                                            break;
                                        }
                                        if (image is ImageBitmapFile bitmapFile)
                                        {
                                            if (bitmapFile.ImageWidth >= minWidth && bitmapFile.ImageHeight >= minHeight)
                                            {
                                                SoftwareBitmap? previewImage = bitmapFile.GetPreviewImage();
                                                if (previewImage != null)
                                                {
                                                    list.Add(previewImage);
                                                    countIMG--;
                                                }
                                            }
                                            continue;
                                        }
                                        if (image is ImageDDSFile ddsImage && img is IMGV5File imgv5)
                                        {
                                            if (ddsImage.ImageWidth >= minWidth && ddsImage.ImageHeight >= minHeight)
                                            {
                                                SoftwareBitmap? previewImage = imgv5.GetDDSFile(ddsImage.DDSIndex)?.GetMipmap()?.GetPreviewImage(ddsImage);
                                                if (previewImage != null)
                                                {
                                                    list.Add(previewImage);
                                                    countIMG--;
                                                }
                                            }
                                            continue;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception) { }

            return list;
        }

        public static Dictionary<string, byte[]> GetPreviewSoundsFromPath(string path)
        {
            Dictionary<string, byte[]> dic = [];

            try
            {
                using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
                using var reader = new BinaryReader(fileStream);
                NPKHeader Header = new(reader);
                if (Header.ValidateFlag())
                {

                    int oggCount = Header.Count;
                    if (oggCount > 0)
                    {
                        long indexOffset = reader.BaseStream.Position;
                        int max = LocalStorageHelper.GetNPKPreviewSoundCount();
                        for (int i = 0; i < oggCount; i++)
                        {
                            if (dic.Count >= max)
                            {
                                break;
                            }
                            reader.BaseStream.Position = indexOffset;
                            NPKIndex index = new(reader);
                            indexOffset = reader.BaseStream.Position;
                            if (index.Size > 0)
                            {
                                reader.BaseStream.Position = index.Offset;
                                byte[] oggBytes = reader.ReadBytes(index.Size);
                                dic.Add(index.Name, oggBytes);
                            }
                        }
                    }
                }
            }
            catch (Exception) { }

            return dic;
        }

        private static async Task<IRandomAccessStream> ConvertByteToStream(byte[] byteData)
        {
            InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();

            using (DataWriter writer = new DataWriter(stream))
            {
                writer.WriteBytes(byteData);
                await writer.StoreAsync();
                writer.DetachStream();
            }

            stream.Seek(0);
            return stream;
        }

        public static byte[] CalculateChecksumBytes(byte[] bytes)
        {
            if (bytes != null && bytes.Length > 0)
            {

                int validLength = bytes.Length / 17 * 17;
                byte[] validBytes = new byte[validLength];
                Array.Copy(bytes, validBytes, validLength);
                return SHA256.HashData(validBytes);
            }
            return [];
        }
        public byte[] CalculateChecksumBytes(List<NPKIndex> list)
        {
            List<byte> byteList = [];
            if (Header != null)
            {
                byteList.AddRange(Header.GetBytes());
                foreach (var index in list)
                {
                    byteList.AddRange(index.GetBytes());
                }
                byte[] bytes = [.. byteList];

                return CalculateChecksumBytes(bytes);
            }
            return [];
        }

        public byte[] CalculateChecksumBytes()
        {
            List<byte> byteList = [];
            if (Header != null) {
                byteList.AddRange(Header.GetBytes());
                foreach (var index in npkIndexs.Values)
                {
                    byteList.AddRange(index.GetBytes());
                }
                byte[] bytes = [.. byteList];

                return CalculateChecksumBytes(bytes);
            }
            return [];
        }

        public bool ValidateChecksum()
        {
            byte[] readBytes = checksumBytes;
            byte[] calBytes = CalculateChecksumBytes();
            return ByteHelper.BytesEuqals(readBytes, calBytes);
        }

        public bool ValidateChecksum(List<NPKIndex> list)
        {
            byte[] readBytes = checksumBytes;
            byte[] calBytes = CalculateChecksumBytes(list);
            return ByteHelper.BytesEuqals(readBytes, calBytes);
        }
    }
}
